﻿using System;
using System.Net;
using System.Net.Http;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;

using Pagination;
using Pagination.Models;

using MiniSched.Data;

namespace MiniSched.Web.Areas.Api {

    public abstract class EntityApiController<TEntity> : MiniSchedApiController where TEntity : class, IMiniSchedEntity {

        internal EntityApiController() { }

        internal virtual TEntity Create(TEntity model) {
            throw new NotSupportedException();
        }

        internal virtual void Update(TEntity entity, TEntity model) {
            throw new NotSupportedException();
        }

        internal virtual object CreatedData(TEntity entity) {
            return new {
                entity.Id
            };
        }

        internal virtual object UpdatedData(TEntity entity) {
            return CreatedData(entity);
        }

        public IHttpActionResult Post(TEntity model) {
            if (ModelState.IsValid) {

                var context = Context;
                var entity = Create(model);
                var entry = context.ChangeTracker.Entries().FirstOrDefault(e => e.Entity == entity);
                if (entry == null || entry.State == EntityState.Detached) {
                    context.Set<TEntity>().Add(entity);
                }
                context.SaveChanges();

                return Created(new Uri(Url.Link("DefaultApi", new { Id = entity.Id })), CreatedData(entity));
            }

            return BadRequest(ModelState);
        }

        public IHttpActionResult Put(TEntity model) {
            if (ModelState.IsValid) {

                var context = Context;
                var entity = context.Set<TEntity>().Find(model.Id);
                if (entity == null) {
                    return NotFound();
                }

                Update(entity, model);

                try {
                    context.SaveChanges();
                }
                catch (DbUpdateConcurrencyException) {
                    return NotFound();
                }

                return Ok(UpdatedData(entity));
            }

            return BadRequest(ModelState);
        }

        public IHttpActionResult Delete(int id) {

            var context = Context;
            var set = context.Set<TEntity>();
            var entity = set.Find(id);
            if (entity == null) {
                return NotFound();
            }

            set.Remove(entity);

            try {
                context.SaveChanges();
            }
            catch (DbUpdateConcurrencyException) {
                return NotFound();
            }

            return Ok();
        }
    }

    public abstract class EntityApiController<TEntity, TRequestModel> : EntityApiController<TEntity> where TEntity : class, IMiniSchedEntity where TRequestModel : EntityRequestModel, new() {

        internal PageSourceFactory PageSourceFactory {
            get { return _PageSourceFactory ?? (_PageSourceFactory = new PageSourceFactory { MaxItemsPerPage = 100, DefaultItemsPerPage = 20 }); }
            set { _PageSourceFactory = value; }
        }
        private PageSourceFactory _PageSourceFactory;

        internal EntityApiController() { }

        internal virtual IQueryable<TEntity> GetSource(TRequestModel model) {
            model = model ?? new TRequestModel();

            var set = Context.Set<TEntity>();
            if (set == null) throw new InvalidOperationException("The set cannot be null.");

            var source = set.AsQueryable();
            if (source == null) throw new InvalidOperationException("The source cannot be null.");

            var id = model.Id;
            if (id.HasValue) source = source.Where(e => e.Id == id.Value);

            return source;
        }

        internal virtual IOrderedQueryable<TEntity> OrderSource(IQueryable<TEntity> source, TRequestModel model) {
            if (null == source) throw new ArgumentNullException("source");
            return source.OrderBy(e => e.Id);
        }

        internal virtual IOrderedPageSourceModel<TEntity, TRequestModel> GetPageSource(TRequestModel model) {
            model = model ?? new TRequestModel();
            return PageSourceFactory.CreateSource(OrderSource(GetSource(model), model), model);
        }

        internal virtual IPageResultsModel<TEntity, TRequestModel> GetPageResults(TRequestModel model) {
            return GetPageSource(model).Query();
        }

        internal virtual IQueryable<object> SelectFrom(IQueryable<TEntity> source) {
            return source.Select(e => e);
        }

        public virtual IHttpActionResult Get(TRequestModel model) {
            model = model ?? new TRequestModel();

            var results = GetPageResults(model);

            return Ok(new {
                results.TotalItemCount,
                results.TotalPageCount,
                Results = SelectFrom(results.Results)
            });
        }
    }
}
